package net.mingsoft.store.action;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.db.Db;
import cn.hutool.http.HttpUtil;
import com.alibaba.druid.pool.DruidDataSource;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import net.mingsoft.base.entity.ResultJson;
import net.mingsoft.basic.biz.IModelBiz;
import net.mingsoft.basic.biz.IRoleModelBiz;
import net.mingsoft.basic.entity.ModelEntity;
import net.mingsoft.basic.entity.RoleModelEntity;
import net.mingsoft.basic.util.BasicUtil;
import net.mingsoft.basic.util.SpringUtil;
import net.mingsoft.basic.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;
import java.util.zip.ZipException;

@Controller("upgrader")
@RequestMapping("/${ms.manager.path}/store")
public class UpgraderAction extends net.mingsoft.basic.action.BaseAction{

	private String html = "<html>" + "<head>" + "<title>价值源自分享！</title>" + "</head>"
			+ "<body style=\"padding-top: 5%;background-color: #ffffff;\">" + "<center>"
			+ "	<img src=\"https://cdn.mingsoft.net/global/mstore/{result/}.png\" />" + "	<div>"
			+ "		<p style=\"clear: both; margin: 30px auto 20px auto; line-height: 35px; font-size: 20px; color: #7e7e7e;\">{message/}</p>"
			+ "	</div>" + "</center>" + "</body>" + "</html>";

	/**
	 * 检测是否有新版本更新请求地址
	 */
	@Value("${ms.mstore.http:http://store.mingsoft.net}")
	private String MS_MSTORE_HTTP;

	/**
	 * 检测是否有新版本更新请求地址
	 */
	@Value("${ms.mstore.host:store.mingsoft.net}")
	private String MS_MSTORE_HOST;

	@Autowired
	private IModelBiz modelBiz;

	@Autowired
	private IRoleModelBiz roleModelBiz;

	@Autowired
	private DataSource dataSource;

	// 第一步检测是否有更新,主页显示图标

	/**
	 * 检测是否有新版本,
	 *
	 * @param request
	 */
	@ResponseBody
	@RequestMapping(value = "/sync", method = RequestMethod.GET)
	public void sync(HttpServletRequest request, HttpServletResponse response) {
		String sync = HttpUtil.createPost(MS_MSTORE_HTTP + "/store/sync.do").header("ms", "upgrader").execute().body();
		this.outJson(response, sync);
	}

	/**	菜单新增递归方法
	 * @param modelParent 菜单实体
	 * @param parentIds 父级ids
	 * @param mangerRoleId  管理员id
	 * @param roleModels 角色mode数组
	 * @param parentId 父级id
	 */
	private void reModel(ModelEntity modelParent,String parentIds,int mangerRoleId,List<RoleModelEntity> roleModels,int parentId){
		//判断是否有子集，有则是菜单没有则是功能
		modelParent.setModelIsMenu(ObjectUtil.isNotNull(modelParent.getModelChildList())&&modelParent.getModelChildList().size()>0
				? Integer.valueOf(1):Integer.valueOf(0));
		//设置父级id,0不会存进数据库
		modelParent.setModelId(parentId);
		modelParent.setModelDatetime(new Timestamp(System.currentTimeMillis()));
		modelParent.setModelParentIds(parentIds);
		ModelEntity modelParentEntity = modelBiz.getEntityByModelCode(modelParent.getModelCode());
		if (modelParentEntity == null) {
			modelBiz.saveEntity(modelParent);
			RoleModelEntity roleModel = new RoleModelEntity();
			roleModel.setRoleId(mangerRoleId);
			roleModel.setModelId(modelParent.getIntId());
			roleModels.add(roleModel);
		} else {
			modelParent.setIntId(modelParentEntity.getModelId());
			modelBiz.updateEntity(modelParent);
		}
		if(ObjectUtil.isNotNull(modelParent.getModelChildList())&&modelParent.getModelChildList().size()>0){
			for (ModelEntity modelEntity : modelParent.getModelChildList()) {
				reModel(modelEntity, StringUtils.isBlank(parentIds)?modelParent.getId():parentIds+","+modelParent.getId(),mangerRoleId,roleModels,modelParent.getIntId());
			}
		}

	}
	/**
	 * 更新软件<br/>
	 * 更具更新列表文件清单请求资源中心服务器，并将请求文件覆盖本地结果 1版本号 2更新描述 3zip下载地址(服务器) 4数据库地址(服务器)
	 *
	 * @throws IOException
	 * @throws ClientProtocolException
	 */
	@ResponseBody
	@RequestMapping("/setup")
	public void setup(HttpServletRequest request, HttpServletResponse response)
			throws ClientProtocolException, IOException {
		//  需要用户决定是否要初始化data数据
		boolean dataInit = true;
		String cookie = BasicUtil.getString("cookie");
		String id = BasicUtil.getString("id");
		// 生成一个httpclient对象
		CloseableHttpClient httpclient = HttpClients.createDefault();
		String url = MS_MSTORE_HTTP + "/people/store/" + id + "/setup.do";
		HttpPost httpPost = new HttpPost(url);
		httpPost.setHeader("Host", MS_MSTORE_HOST);
		httpPost.setHeader("ms", "upgrader");
		httpPost.setHeader("accept", "");
		httpPost.setHeader("method", "setup");
		httpPost.setHeader("cookie", cookie);
		httpPost.setHeader("Content-Type", "text/html;charset=UTF-8");
		HttpResponse httpResponse = null;
		try {
			httpResponse = httpclient.execute(httpPost);
		} catch (HttpHostConnectException e) {
			e.printStackTrace();
			this.outString(response, html.replace("{result/}", "false").replace("{message/}", "链接MStore失败，请回到主界面重试！"));
			return;
		}
		HttpEntity entity = httpResponse.getEntity();
		InputStream in = entity.getContent();

		String zipfile = "";
		String source = "";
		String menuStr = "";
		try {
			zipfile = httpResponse.getHeaders("fileName")[0].getValue();
			source = httpResponse.getHeaders("source")[0].getValue();
		} catch (ArrayIndexOutOfBoundsException e) {
			this.outString(response, html.replace("{result/}", "false").replace("{message/}", "安装包获取失败，请回到主界面重试！"));
			return;
		}

		if (StringUtil.isBlank(zipfile)) {
			return;
		}

		File dir =FileUtil.file(BasicUtil.getRealPath("temp"));
		// 判断classes文件是否存在，不存在就创建
		if (!FileUtil.exist(dir)) {
			FileUtil.mkdir(dir);
		}

		File zFile = FileUtil.file(dir,zipfile);
		try {
			FileOutputStream fout = new FileOutputStream(zFile);
			int l = -1;
			byte[] tmp = new byte[1024];
			while ((l = in.read(tmp)) != -1) {
				fout.write(tmp, 0, l);
				// 注意这里如果用OutputStream.write(buff)的话，图片会失真，大家可以试试
			}
			fout.flush();
			fout.close();
		} finally {
			// 关闭低层流。
			in.close();
		}
		httpclient.close();

		String entryName = "";
		List<File> initFiles = new ArrayList<File>();
		List<File> ddlSqls = new ArrayList<File>();
		List<File> dataSqls = new ArrayList<File>();
		List<File> files = new ArrayList<File>();
		List<File> classFiles = new ArrayList<File>();
		File menu = null;


		// 更新文件
		try {
			ZipFile zipFile = new ZipFile(zFile);

			// 创建本zip文件解压目录

			// 得到zip文件条目枚举对象
			Enumeration<? extends ZipEntry> zipEnum = zipFile.getEntries();
			// 定义输入输出流对象
			// 循环读取条目
			while (zipEnum.hasMoreElements()) {
				// 得到当前条目
				ZipEntry entry = (ZipEntry) zipEnum.nextElement();
				try {
					entryName = new String(entry.getName().getBytes("utf-8"));
					if(entryName.indexOf(".DS_Store") > -1) {
						continue;
					}
					//创建包结构
					if(entryName.indexOf(File.separator)> -1) {
						FileUtil.mkdir(entryName.substring(0,entryName.indexOf(File.separator)));
					}
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 若当前条目为目录则创建
				if (entry.isDirectory()) {
					new File(dir.getAbsolutePath() + File.separator + entryName).mkdirs();
				} else {
					// 若当前条目为文件则解压到相应目录
					try {
						File temp = new File(dir.getAbsolutePath() + File.separator + entryName);
						InputStream input = zipFile.getInputStream(entry);
						OutputStream output = new FileOutputStream(temp);
						// 纪录sql
						if (entryName.indexOf(".sql") > 0) {
							if(entryName.indexOf("ddl.sql") > 0){
								ddlSqls.add(new File(dir.getAbsolutePath() + File.separator + entryName));
							}else if(entryName.indexOf("data.sql") > 0) {
								dataSqls.add(new File(dir.getAbsolutePath() + File.separator + entryName));
							}else {
								initFiles.add(new File(dir.getAbsolutePath() + File.separator + entryName));
							}
						}
						if (entryName.indexOf(".class") > 0) {
							classFiles.add(new File(dir.getAbsolutePath() + File.separator + entryName));
						}
						if (entryName.indexOf(".json") > 0) {
							menu = new File(dir.getAbsolutePath() + File.separator + entryName);
						}
						byte[] buffer = new byte[1024 * 8];
						int readLen = 0;
						while ((readLen = input.read(buffer, 0, 1024 * 8)) != -1) {
							output.write(buffer, 0, readLen);
						}
						output.flush();
						output.close();
						input.close();
						input = null;
						output = null;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			}
			zipFile.close();

		} catch (ZipException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		// 执行sql
		if (initFiles.size() > 0 || ddlSqls.size() > 0 || dataSqls.size() > 0){
			try {
				// 先执行ddl
				for (File sql : ddlSqls) {
					Resource resource = new UrlResource(sql.toURI());
					ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
				}
				Map<String, Integer> map = new HashMap<>();
				map.put("app_id", BasicUtil.getApp().getAppId());
				for (File sql : initFiles) {
					Template template = new Template("sql", new FileReader(sql), null);
					String sqlStr = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
					if(StringUtils.isNotBlank(sqlStr)){
						FileUtil.writeUtf8String(sqlStr,sql);
						Resource resource = new UrlResource(sql.toURI());
						ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
					}
				}
				if(dataInit){
					for (File sql : dataSqls) {
						Template template = new Template("sql", new FileReader(sql), null);
						String sqlStr = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
						if(StringUtils.isNotBlank(sqlStr)){
							FileUtil.writeUtf8String(sqlStr,sql);
							Resource resource = new UrlResource(sql.toURI());
							ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
						}
					}
				}
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (TemplateException e) {
				e.printStackTrace();
			} catch (SQLException throwables) {
				throwables.printStackTrace();
			}
		}

		// 读取菜单
		if (menu != null){
			menuStr = FileUtil.readUtf8String(menu);
		}

		// 组织菜单数据和权限
		int mangerRoleId = this.getManagerBySession().getRoleId();
		modelBiz.jsonToModel(menuStr,mangerRoleId);
		// 执行class
		if (classFiles.size() > 0) {
			ClassLoader cload = new URLClassLoader(
					new URL[] { dir.toURI().toURL() },
					Thread.currentThread().getContextClassLoader());
			try {
				for (File cls : classFiles) {
					String className = cls.getPath().substring(dir.getPath().length()+1,
							cls.getPath().length() - 6);
					className = className.replace("/", ".").replace("\\", ".");
					Class clzss = Class.forName(className, true, cload);
					Object obj = clzss.newInstance();
					Method[] method = clzss.getDeclaredMethods();
					for (Method m : method) {
						if (m.toString().indexOf("public ") > -1) {
							try {
								Object returnObj = m.invoke(obj, new Object[] {});
								if (returnObj instanceof ResultJson) {
									ResultJson result = (ResultJson) returnObj;
									if (!result.isResult()) {
										this.outString(response, html.replace("{result/}", "false")
												.replace("{message/}", result.getResultMsg()));
										return;
									}
								}
							} catch (IllegalArgumentException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (InvocationTargetException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}

						}

					}
				}

			} catch (ClassNotFoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (InstantiationException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

		}

		// 清理文件
		FileUtil.del(dir);
		// 检查当前系统是拥有代码
		if (StringUtils.isNotBlank(source)) {
			ResultJson result = new ResultJson();
			if (source.equals("yes")) {
				this.outString(response, html.replace("{result/}", "true").replace("{message/}",
						"安装成功，请下载源码，并编译重启！" + " <form  method=\"post\" target='_blank' action=" + MS_MSTORE_HTTP
								+ "/people/store/plugin/download.do>" + "<input name=\"id\" style=\"display:none\" value=\""
								+ id + "\"/>"
								+ "<input  type=\"submit\" value=\"下载源码\" style='    width: 104px;height: 34px;border: none;background-color: #0099ff;color: #ffffff;font-size: 14px;cursor: pointer;'/>"
								+ "</form> "));
				return;
			}
		}
		this.outString(response, html.replace("{result/}", "true").replace("{message/}", "安装升级成功！"));
	}

	private boolean checkModel(String className) {
		try {
			Class cls = Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
}
